"""
Generates the serialize.h header file, containing Clarabel's settings names.
"""

import argparse
from pathlib import Path

from python import runfiles

_FIELDS_TO_SKIP = {
    # These are enums, which we don't support yet.
    "chordal_decomposition_merge_method",
    "direct_solve_method",
}

_PROLOGUE = """\
#pragma once

#include "drake/common/name_value.h"

// This file helps Drake's solvers/clarabel_solver.cc set the Clarabel options.
//
// It is committed to source control to simplify the build process, but can be
// mechanically regenerated by running the `gen_serialize` program. A linter
// checks that the committed code matches what would be regenerated.

namespace clarabel {

template <typename Archive>
// NOLINTNEXTLINE(runtime/references)
void Serialize(Archive* a, DefaultSettings<double>& settings) {
#define DRAKE_VISIT(x) a->Visit(drake::MakeNameValue(#x, &(settings.x)))
"""

_EPILOGUE = """\
#undef DRAKE_VISIT
}

}  // namespace clarabel
"""


def _settings_names():
    """Returns the list of names of Clarabel.cpp's settings."""

    # Read the DefaultSettings.h header.
    manifest = runfiles.Create()
    headers_dir = "clarabel_cpp_internal/include/cpp"
    header = manifest.Rlocation(f"{headers_dir}/DefaultSettings.h")
    with open(header) as f:
        text = f.read()

    # Strip away the parts we don't need.
    needle = "struct DefaultSettings\n{"
    index = text.find(needle)
    assert index > 0
    text = text[index + len(needle):]
    needle = "}"
    index = text.find(needle)
    assert index > 0
    text = text[:index]

    # Parse the contents of the struct.
    for line in text.splitlines():
        line = line.strip()
        if not line:
            continue
        if line.startswith("static"):
            continue
        if line.startswith("#ifdef"):
            continue
        if line.startswith("#endif"):
            continue
        assert line.endswith(";"), line
        line = line[:-1]
        assert line.count(" ") == 1
        _, name = line.split()
        yield name


def _create_header_text():
    result = _PROLOGUE
    for name in _settings_names():
        if name in _FIELDS_TO_SKIP:
            result += f"  // skipped: {name}\n"
        else:
            result += f"  DRAKE_VISIT({name});\n"
    result += _EPILOGUE
    return result


def _main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--output", metavar="FILE", required=True)
    args = parser.parse_args()
    text = _create_header_text()
    with open(args.output, "w", encoding="utf-8") as f:
        f.write(text)


assert __name__ == "__main__"
_main()
